<?php

//declare(strict_types=1);
/**
 * Created by PhpStorm.
 *​
 * Protobuf.php
 *
 * protobuf协议文件
 *
 * User：YM
 * Date：2019/12/24
 * Time：下午5:07
 */


namespace Ym\AliyunSls;


/**
 * Protobuf
 * Class to aid in the parsing and creating of Protocol Buffer Messages
 * This class should be included by the developer before they use a
 * generated protobuf class.
 */
class Protobuf {
    const TYPE_DOUBLE   = 1;   // double, exactly eight bytes on the wire.
    const TYPE_FLOAT    = 2;   // float, exactly four bytes on the wire.
    const TYPE_INT64    = 3;   // int64, varint on the wire.  Negative numbers
    // take 10 bytes.  Use TYPE_SINT64 if negative
    // values are likely.
    const TYPE_UINT64   = 4;   // uint64, varint on the wire.
    const TYPE_INT32    = 5;   // int32, varint on the wire.  Negative numbers
    // take 10 bytes.  Use TYPE_SINT32 if negative
    // values are likely.
    const TYPE_FIXED64  = 6;   // uint64, exactly eight bytes on the wire.
    const TYPE_FIXED32  = 7;   // uint32, exactly four bytes on the wire.
    const TYPE_BOOL     = 8;   // bool, varint on the wire.
    const TYPE_STRING   = 9;   // UTF-8 text.
    const TYPE_GROUP    = 10;  // Tag-delimited message.  Deprecated.
    const TYPE_MESSAGE  = 11;  // Length-delimited message.
    const TYPE_BYTES    = 12;  // Arbitrary byte array.
    const TYPE_UINT32   = 13;  // uint32, varint on the wire
    const TYPE_ENUM     = 14;  // Enum, varint on the wire
    const TYPE_SFIXED32 = 15;  // int32, exactly four bytes on the wire
    const TYPE_SFIXED64 = 16;  // int64, exactly eight bytes on the wire
    const TYPE_SINT32   = 17;  // int32, ZigZag-encoded varint on the wire
    const TYPE_SINT64   = 18;  // int64, ZigZag-encoded varint on the wire
    /**
     * Returns a string representing this wiretype
     */
    public static function get_wiretype($wire_type) {
        switch ($wire_type) {
            case 0: return 'varint';
            case 1: return '64-bit';
            case 2: return 'length-delimited';
            case 3: return 'group start';
            case 4: return 'group end';
            case 5: return '32-bit';
            default: return 'unknown';
        }
    }
    /**
     * Returns how big (in bytes) this number would be as a varint
     */
    public static function size_varint($i) {
        /*		$len = 0;
                do {
                    $i = $i >> 7;
                    $len++;
                } while ($i != 0);
                return $len;
        */
        // TODO Change to a binary search
        if ($i < 0x80)
            return 1;
        if ($i < 0x4000)
            return 2;
        if ($i < 0x200000)
            return 3;
        if ($i < 0x10000000)
            return 4;
        if ($i < 0x800000000)
            return 5;
        if ($i < 0x40000000000)
            return 6;
        if ($i < 0x2000000000000)
            return 7;
        if ($i < 0x100000000000000)
            return 8;
        if ($i < 0x8000000000000000)
            return 9;
    }
    /**
     * read_varint
     * 原来注释有注解报错
     * Tries to read a varint from $fp.
     * User：YM
     * Date：2019/12/31
     * Time：上午12:38
     * @static
     * @param $fp
     * @param null $limit
     * @return bool|int , the Varint from the stream, or false if the stream has reached eof
     */
    public static function read_varint($fp, &$limit = null) {
        $value = '';
        $len = 0;
        do { // Keep reading until we find the last byte
            $b = fread($fp, 1);
            if ($b === false)
                throw new Exception("read_varint(): Error reading byte");
            if (strlen($b) < 1)
                break;
            $value .= $b;
            $len++;
        } while ($b >= "\x80");
        if ($len == 0) {
            if (feof($fp))
                return false;
            throw new Exception("read_varint(): Error reading byte");
        }
        if ($limit !== null)
            $limit -= $len;
        $i = 0;
        $shift = 0;
        for ($j = 0; $j < $len; $j++) {
            $i |= ((ord($value[$j]) & 0x7F) << $shift);
            $shift += 7;
        }
        return $i;
    }
    public static function read_double($fp){throw new Exception("I've not coded it yet Exception");}
    public static function read_float ($fp){throw new Exception("I've not coded it yet Exception");}
    public static function read_uint64($fp){throw new Exception("I've not coded it yet Exception");}
    public static function read_int64 ($fp){throw new Exception("I've not coded it yet Exception");}
    public static function read_uint32($fp){throw new Exception("I've not coded it yet Exception");}
    public static function read_int32 ($fp){throw new Exception("I've not coded it yet Exception");}
    public static function read_zint32($fp){throw new Exception("I've not coded it yet Exception");}
    public static function read_zint64($fp){throw new Exception("I've not coded it yet Exception");}
    /**
     * Writes a varint to $fp
     * returns the number of bytes written
     * @param $fp
     * @param $i The int to encode
     * @return The number of bytes written
     */
    public static function write_varint($fp, $i) {
        $len = 0;
        do {
            $v = $i & 0x7F;
            $i = $i >> 7;
            if ($i != 0)
                $v |= 0x80;
            if (fwrite($fp, chr($v)) !== 1)
                throw new Exception("write_varint(): Error writing byte");
            $len++;
        } while ($i != 0);
        return $len;
    }
    public static function write_double($fp, $d){throw new Exception("I've not coded it yet Exception");}
    public static function write_float ($fp, $f){throw new Exception("I've not coded it yet Exception");}
    public static function write_uint64($fp, $i){throw new Exception("I've not coded it yet Exception");}
    public static function write_int64 ($fp, $i){throw new Exception("I've not coded it yet Exception");}
    public static function write_uint32($fp, $i){throw new Exception("I've not coded it yet Exception");}
    public static function write_int32 ($fp, $i){throw new Exception("I've not coded it yet Exception");}
    public static function write_zint32($fp, $i){throw new Exception("I've not coded it yet Exception");}
    public static function write_zint64($fp, $i){throw new Exception("I've not coded it yet Exception");}
    /**
     * Seek past a varint
     */
    public static function skip_varint($fp) {
        $len = 0;
        do { // Keep reading until we find the last byte
            $b = fread($fp, 1);
            if ($b === false)
                throw new Exception("skip(varint): Error reading byte");
            $len++;
        } while ($b >= "\x80");
        return $len;
    }
    /**
     * Seek past the current field
     */
    public static function skip_field($fp, $wire_type) {
        switch ($wire_type) {
            case 0: // varint
                return Protobuf::skip_varint($fp);
            case 1: // 64bit
                if (fseek($fp, 8, SEEK_CUR) === -1)
                    throw new Exception('skip(' . ProtoBuf::get_wiretype(1) . '): Error seeking');
                return 8;
            case 2: // length delimited
                $varlen = 0;
                $len = Protobuf::read_varint($fp, $varlen);
                if (fseek($fp, $len, SEEK_CUR) === -1)
                    throw new Exception('skip(' . ProtoBuf::get_wiretype(2) . '): Error seeking');
                return $len - $varlen;
            //case 3: // Start group TODO we must keep looping until we find the closing end grou
            //case 4: // End group - We should never skip a end group!
            //	return 0; // Do nothing
            case 5: // 32bit
                if (fseek($fp, 4, SEEK_CUR) === -1)
                    throw new Exception('skip('. ProtoBuf::get_wiretype(5) . '): Error seeking');
                return 4;
            default:
                throw new Exception('skip('. ProtoBuf::get_wiretype($wire_type) . '): Unsupported wire_type');
        }
    }
    /**
     * Read a unknown field from the stream and return its raw bytes
     */
    public static function read_field($fp, $wire_type, &$limit = null) {
        switch ($wire_type) {
            case 0: // varint
                return Protobuf::read_varint($fp, $limit);
            case 1: // 64bit
                $limit -= 8;
                return fread($fp, 8);
            case 2: // length delimited
                $len = Protobuf::read_varint($fp, $limit);
                $limit -= $len;
                return fread($fp, $len);
            //case 3: // Start group TODO we must keep looping until we find the closing end grou
            //case 4: // End group - We should never skip a end group!
            //	return 0; // Do nothing
            case 5: // 32bit
                $limit -= 4;
                return fread($fp, 4);
            default:
                throw new Exception('read_unknown('. ProtoBuf::get_wiretype($wire_type) . '): Unsupported wire_type');
        }
    }
    /**
     * Used to aid in pretty printing of Protobuf objects
     */
    private static $print_depth = 0;
    private static $indent_char = "\t";
    private static $print_limit = 50;
    public static function toString($key, $value) {
        if (is_null($value))
            return;
        $ret = str_repeat(self::$indent_char, self::$print_depth) . "$key=>";
        if (is_array($value)) {
            $ret .= "array(\n";
            self::$print_depth++;
            foreach($value as $i => $v)
                $ret .= self::toString("[$i]", $v);
            self::$print_depth--;
            $ret .= str_repeat(self::$indent_char, self::$print_depth) . ")\n";
        } else {
            if (is_object($value)) {
                self::$print_depth++;
                $ret .= get_class($value) . "(\n";
                $ret .= $value->__toString() . "\n";
                self::$print_depth--;
                $ret .= str_repeat(self::$indent_char, self::$print_depth) . ")\n";
            } elseif (is_string($value)) {
                $safevalue = addcslashes($value, "\0..\37\177..\377");
                if (strlen($safevalue) > self::$print_limit) {
                    $safevalue = substr($safevalue, 0, self::$print_limit) . '...';
                }
                $ret .= '"' . $safevalue . '" (' . strlen($value) . " bytes)\n";

            } elseif (is_bool($value)) {
                $ret .= ($value ? 'true' : 'false') . "\n";
            } else {
                $ret .= (string)$value . "\n";
            }
        }
        return $ret;
    }
}